/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <errno.h>

#include "ohos_init.h"
#include "kv_store.h"
#include "sys/time.h"
#include "lwip/inet.h"
#include "lwip/netdb.h"
#include "cmsis_os2.h"
#include "iot_cloud.h"
#include "iot_netcfg.h"
#include "iot_wifi_xradio.h"
#include "iot_log.h"
#include "iot_boardbutton_xradio.h"
#include "iot_boardled_xradio.h"
#include "lock_contrl.h"
#include "iot_profile.h"
#include "iot_demo_def.h"
#include "lock_manager.h"
#include "os_thread.h"

#define DATA_PATH   "//"

#define CLOUD_DEFAULT_SERVERIP          "121.36.42.100"     // replace it with iotplatform server ip
#define CLOUD_DEFAULT_SERVERPORT        "1883"              // replace it with iotplatform server  port
#define CONFIG_TASK_DEFAULT_STACKSIZE   (4 * 1024)         // unit:bytes
#define CONFIG_TASK_DEFAULT_PRIOR       20                 // default task priority
#define CONFIG_FLASHLED_FRENETCONFIG    300                // 8
#define CONFIG_FLASHLED_FREWIFI         500                // 4
#define CONFIG_FLASHLED_FRECLOUD        1000               // 1
#define CONFIG_FLASHLED_WORKSWELL       5000               // 0.1

#define UNLOCK_TIMEOUT                  15                  // 5 minutes start alarm

#define STA_OPEN            1
#define STA_CLOSE           0

#define CN_LOCK_EVENT_SETSTATUS         (1<<0)
#define CN_LOCK_EVENT_SETPSK            (1<<1)
#define CN_LOCK_EVENT_RESETPSK          (1<<2)

#define CN_LOCK_EVENT_ALL               (CN_LOCK_EVENT_SETSTATUS | CN_LOCK_EVENT_SETPSK | CN_LOCK_EVENT_RESETPSK)

#define SSID_KEY        "ssid"
#define PWD_KEY         "password"

typedef enum {
    EN_MONDAY = 0,
    EN_TUESDAY,
    EN_WEDNESDAY,
    EN_THURSDAY,
    EN_FRIDAY,
    EN_SATURDAY,
    EN_SUNDAY,
    EN_DAYALL
} EN_DAY;

typedef struct {
    int option;
    int result;
} LockManager;

typedef struct {
    int lockStatus;
    int curDay;
    int curSecondsInDay;
    LockManager lockManager;
    osEventFlagsId_t lockEvent;
    OS_Thread_t g_main_thread;
    osTimerId_t curTimerCalc;
}ApplicationController;
static ApplicationController g_appController;

typedef enum {
    LOCK_NORMAL,
    LOCK_OPENED_LONG_ERR,
    LOCK_OPEN_PWD_ERROR,
    LOCK_SET_PWD_ERR,

    LOCK_OPEN_SUCCESS = 8,
    LOCK_CLOSE_SUCCESS,
} LOCK_STA;

// ******************************* cmd && cloud report thread ******************************** //
static unsigned char LockCanClose()
{
    const int lockDistance = 4;
    int distance = HCSR04_GetMeasurementDistance();

    printf("distance = %d \n", distance);

    if (distance <= lockDistance) {
        return 1;
    }

    return 0;
}

static void DataCollectAndReport(void *arg)
{
    (void)arg;
    const int waitTicks = (10 * 1000);
    int LockAbnormalFlag = 0;
    int timecount = 0;
    while (1) {
        int LockAbnormal = 0;
        int OriginalPsk = 0;
        uint32_t lockEvent = osEventFlagsWait(g_appController.lockEvent, CN_LOCK_EVENT_ALL, osFlagsWaitAny, waitTicks);
        RaiseLog(LOG_LEVEL_INFO, "GetEvent : %08x", lockEvent);

        if (lockEvent & CN_LOCK_EVENT_SETSTATUS) {
            if (g_appController.lockManager.result) {
                printf("CN_LOCK_EVENT_SETSTATUS \r\n");
                LockSet(g_appController.lockStatus);
                LockAbnormal = LOCK_OPEN_SUCCESS;
            } else {
                LockAbnormal = LOCK_OPEN_PWD_ERROR;
            }
        } else if (lockEvent & CN_LOCK_EVENT_SETPSK) {
            printf("CN_LOCK_EVENT_SETPSK \r\n");
            if (g_appController.lockManager.result != 1) {
            }
        } else if (lockEvent & CN_LOCK_EVENT_RESETPSK) {
            printf("CN_LOCK_EVENT_RESETPSK \r\n");
            OriginalPsk = 1;
            CLOUD_ReportNotification(NOTIFY_TYPE_SECONDARY, "The Password reset!", "密码已重置");
        }

        {
            if (g_appController.lockStatus == STA_OPEN) {
                if (LockCanClose()) {
                    g_appController.lockStatus = STA_CLOSE;
                    LockSet(g_appController.lockStatus);
                    LockAbnormal = LOCK_CLOSE_SUCCESS;
                } else if (++timecount >= UNLOCK_TIMEOUT) {
                    LockAbnormalFlag = 1;
                }
            } else {
                LockAbnormalFlag = 0;
            }

            if (LockAbnormalFlag) {
                LockAbnormal = LOCK_OPENED_LONG_ERR;
                if (timecount >= UNLOCK_TIMEOUT) {
                    CLOUD_ReportNotification(NOTIFY_TYPE_URGENT, "The Door UnLocked!", "门未关");
                    timecount = 0;
                }
            }
        }

        if (LockManagerGetPskNum() <= 0) {
            OriginalPsk = 1;
        }

        (void) IotProfile_Report(LockAbnormal, OriginalPsk);
    }

    return;
}

// *************************** get current timer *************************************** //
static void RtcTimeUpdate(void)
{
    extern int SntpGetRtcTime(int localTimeZone, struct tm *rtcTime);
    struct tm rtcTime;
    SntpGetRtcTime(CONFIG_LOCAL_TIMEZONE, &rtcTime);

    RaiseLog(LOG_LEVEL_INFO, "Year:%d Month:%d Mday:%d Wday:%d Hour:%d Min:%d Sec:%d", \
        rtcTime.tm_year + BASE_YEAR_OF_TIME_CALC, rtcTime.tm_mon + 1, rtcTime.tm_mday,\
        rtcTime.tm_wday, rtcTime.tm_hour, rtcTime.tm_min, rtcTime.tm_sec);

    if (rtcTime.tm_wday > 0) {
        g_appController.curDay = rtcTime.tm_wday - 1;
    } else {
        g_appController.curDay = EN_SUNDAY;
    }

    g_appController.curSecondsInDay = rtcTime.tm_hour * CN_SECONDS_IN_HOUR + \
            rtcTime.tm_min * CN_SECONDS_IN_MINUTE + rtcTime.tm_sec + 8; // add 8 ms offset
}

// *************************** timer thread *************************************** //
static void CurrentTimeCalcTimerHander()
{
    g_appController.curSecondsInDay ++;
    if (g_appController.curSecondsInDay >= CN_SECONS_IN_DAY) {
        g_appController.curSecondsInDay = 0;
        g_appController.curDay++;
        if (g_appController.curDay >= EN_DAYALL) {
            g_appController.curDay = EN_MONDAY;
        }
    }
}

/**
 * @brief Callback function used to process commands delivered by the Cloud Platform
 * @param command Indicates the command delivered by the Cloud Platform
 * @param value Indicates the parameter corresponding to the command
 * @return Returns 0 success while -1 failed
 */
int IotProfile_CommandCallback(int command, void *buf)
{
    int ret = 0;
    CommandParamSetPsk *setPskParam = NULL;
    CommandParamSetLock *setLockParam = NULL;

    switch (command) {
        case CLOUD_COMMAND_SETLOCK:
            setLockParam = (CommandParamSetLock *)buf;
            LockCheckInfo info = {0};
            info.disposable = setLockParam->isDisposable;
            info.curSeconds = g_appController.curSecondsInDay;
            info.curDay = g_appController.curDay;
            strcpy(info.mLock.password, setLockParam->password);
            if (LockManagerCheckup(&info) == 1) {
                g_appController.lockStatus = setLockParam->status;
                g_appController.lockManager.result = 1;
            } else {
              g_appController.lockManager.result = 0;
            }
            g_appController.lockManager.option = OPTION_OPEN;
            RaiseLog(LOG_LEVEL_INFO, "setCurtainParam.status:%d\r\n", setLockParam->status);
            osEventFlagsSet(g_appController.lockEvent, CN_LOCK_EVENT_SETSTATUS);
            break;
        case CLOUD_COMMAND_SETPSK:
            setPskParam = (CommandParamSetPsk *)buf;
            g_appController.lockManager.option = setPskParam->option;
            g_appController.lockManager.result = LockManagerUpdate(setPskParam);
            osEventFlagsSet(g_appController.lockEvent, CN_LOCK_EVENT_SETPSK);
            break;
        default:
            return -1;
    }

    return ret;
}


// ************************************* main thread ************************************************** //
static int IotWifiInfo_get(char *ssid, int id_size, char *pwd, int pd_size)
{
    int retval = UtilsGetValue(SSID_KEY, ssid, id_size);
    if (retval <= 0) {
        RaiseLog(LOG_LEVEL_ERR, "no such ssid stored! \n");
        return -1;
    }

    if (UtilsGetValue(PWD_KEY, pwd, pd_size) < 0) {
        RaiseLog(LOG_LEVEL_INFO, "ssid(%s) no password stored! \n", ssid);
    } else {
        RaiseLog(LOG_LEVEL_INFO, "ssid : %s, pwd : %s! \n", ssid, pwd);
    }

    return 0;
}

static void IotWifiInfo_set(char *ssid, char *pwd)
{
    if (UtilsSetValue(SSID_KEY, ssid) != 0) {
        RaiseLog(LOG_LEVEL_ERR, "store ssid failed! \n");
        return;
    }
    if (UtilsSetValue(PWD_KEY, pwd) != 0) {
        RaiseLog(LOG_LEVEL_ERR, "store password failed! \n");
        UtilsDeleteValue(SSID_KEY);
        return;
    }
    RaiseLog(LOG_LEVEL_INFO, "store password success! \n");
}

static int ButtonEventHandle(ButtonEvent *event)
{
    LOG_I("event_id = %d, event_value = %d \n", event->button_id, event->value);
    if (event->value == BUTTON_LONG_PRESS) {
        LOG_I("button long pressed!\n");
        LockManagerClearPsk();
        osEventFlagsSet(g_appController.lockEvent, CN_LOCK_EVENT_RESETPSK);
    }
    if (event->value == BUTTON_PRESS) {
        LOG_I("button pressed!\n");
        g_appController.lockStatus = STA_OPEN;
        g_appController.lockManager.result = 1;
        osEventFlagsSet(g_appController.lockEvent, CN_LOCK_EVENT_SETSTATUS);
    }

    return 0;
}

static void SmartLockThread(void *arg)
{
    osThreadAttr_t attr;
    const int waitTicks = 1000;
    int button_pressed;
    char ssid[128] = {0};
    char psk[128] = {0};
    const char *app_name = "teamX-SmartLock01";
    const char *deviceID = "61bae4814d9b0202871982f0_SmartLock01";
    const char *devicePSK = "12345678";
    LOG_I("start SmartLockThread!");
    LedFlashTaskStart();
    memset(&g_appController.lockManager, 0x00, sizeof(LockManager));
    LedFlashFrequencySet(LED_FLASH_FAST);
    button_pressed =  Board_IsButtonPressed();
    LOG_I("button is %s \n", button_pressed ? "pressed!" : "not press!");
    UtilsSetEnv(DATA_PATH);
    Board_ButtonInit(ButtonEventHandle);
    sleep(1);
    LockInit();
    HCSR04_Init();

    if (button_pressed) {
        if (BOARD_NetCfgStartConfig(app_name, ssid, sizeof(ssid), psk, sizeof(psk)) < 0) {
            LOG_E("BOARD_NetCfgStartConfig failed!");
            return;
        }
    } else {
        button_pressed = IotWifiInfo_get(ssid, sizeof(ssid), psk, sizeof(psk));
        if (button_pressed != 0) {
            button_pressed = 1;
            if (BOARD_NetCfgStartConfig(app_name, ssid, sizeof(ssid), psk, sizeof(psk)) < 0) {
                LOG_E("BOARD_NetCfgStartConfig failed!");
                return;
            }
        }
    }

    LedFlashFrequencySet(LED_FLASH_NORMAL);
    LOG_I("config network ok! start connect the network!");
    if (BOARD_ConnectWifi((const char *)ssid, (const char *)psk) != 0) {
        LOG_E("connect wifi failed! ssid:%s, psk:%s", ssid, psk);
        LedFlashFrequencySet(LED_FLASH_FAST_PLUGS);
        return;
    }
    sleep(1);
    LOG_I("connect network success!");
    if (button_pressed) {
        IotWifiInfo_set((char *)ssid, (char *)psk);
    }

    LedFlashFrequencySet(LED_FLASH_SLOW);

    RtcTimeUpdate();

    LockManagerInit(g_appController.curDay);

    if (CLOUD_Init() != 0) {
        LOG_E("CLOUD_Init failed!");
        return;
    }

    if (CLOUD_Connect(deviceID, devicePSK, CLOUD_DEFAULT_SERVERIP, CLOUD_DEFAULT_SERVERPORT) != 0) {
        LOG_E("CLOUD_Connect failed!");
        LedFlashFrequencySet(LED_FLASH_FAST_PLUGS);
        return;
    }
    LedFlashFrequencySet(LED_FLASH_SLOW_PLUGS);

    g_appController.curTimerCalc = osTimerNew(CurrentTimeCalcTimerHander, osTimerPeriodic, NULL, NULL);
    osTimerStart(g_appController.curTimerCalc, waitTicks);

    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = CONFIG_TASK_DEFAULT_STACKSIZE;
    attr.priority = CONFIG_TASK_DEFAULT_PRIOR;
    attr.name = "DataCollectAndReport";
    if (osThreadNew((osThreadFunc_t)DataCollectAndReport, NULL, (const osThreadAttr_t *)&attr) == NULL) {
        return;
    }
}

int SmartLockMain(int argc, char **argv)
{
    printf("start app demo\r\n");
    memset(&g_appController, 0x00, sizeof(g_appController));
    g_appController.lockEvent = osEventFlagsNew(NULL);
    if (OS_ThreadCreate(&(g_appController.g_main_thread), "SmartLockThread", SmartLockThread,
        NULL, OS_THREAD_PRIO_APP, CONFIG_TASK_DEFAULT_STACKSIZE) != OS_OK) {
        printf("[ERR] Create SmartLockThread Failed\n");
    }
}

SYS_RUN(SmartLockMain);
